package cn.seaboot.admin.flake.manager;

import cn.seaboot.commons.exception.BizException;
import cn.seaboot.commons.lang.Warning;
import cn.seaboot.flake.core.Flake;
import cn.seaboot.flake.core.FlakeTemplate;
import cn.seaboot.flake.core.FlakeTokenizer;
import cn.seaboot.flake.core.PreparedSql;
import cn.seaboot.flake.exec.FlackException;
import cn.seaboot.flake.mapping.Mapper;
import cn.seaboot.flake.mapping.MappingProvider;
import cn.seaboot.flake.mapping.ParameterMap;
import cn.seaboot.flake.mapping.ResultMap;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.StatementType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.List;
import java.util.Map;

/**
 * 封装型代码执行器
 * <p>
 * 此处的代码，已经非常接近 mybatis 了，提供查询语句的 id，即可完成语句的调用，并反馈执行结果。
 * <p>
 * 欠缺部分：日常的业务需求，一般需要权限控制，系统需要增加对应的模块进行管控。
 *
 * @author Mr.css
 * @version 2022-02-15 16:18
 */
@Service
public class FlakeExecutor implements Executor {
    private final Logger logger = LoggerFactory.getLogger(FlakeExecutor.class);
    /**
     * 不同的数据库，对应不同的Flake
     */
    private Flake flake = new Flake();
    /**
     * 映射扫描
     */
    @Resource
    private MappingProvider mappingProvider;

    /**
     * 查询信息配置
     *
     * @param mapperGroup 映射分组
     * @param id          配置ID
     * @return 映射信息
     */
    @Override
    public Mapper queryMapperById(String mapperGroup, String id) {
        return mappingProvider.queryMapperById(mapperGroup, id);
    }

    /**
     * 程序总入口，按照ID自动检索配置，并且获得最终的 执行结果
     *
     * @param mapperGroup 表名
     * @param id          配置ID
     * @param params      参数
     * @return 执行结果
     */
    @Override
    public Object execute(String mapperGroup, String id, Object params) {
        Mapper mapper = mappingProvider.queryMapperById(mapperGroup, id);
        Assert.notNull(mapper, "mapper is undefined: " + mapperGroup + "." + id);
        if (logger.isDebugEnabled()) {
            logger.debug("Flake execute ===>: {}.{}", mapper.getDatabaseId(), mapper.getId());
        }
        if (StatementType.CALLABLE.equals(mapper.getStatementType())) {
            return this.processCall(mapper, params);
        } else {
            return this.executeCommand(mapper, params);
        }
    }

    /**
     * 执行一条命令
     *
     * @param mapper 映射配置
     * @param params 参数
     * @return 运行结果
     */
    @Override
    public Object executeCommand(Mapper mapper, Object params) {
        switch (mapper.getCommandType()) {
            case INSERT:
            case UPDATE:
            case DELETE:
                return this.executeUpdate(mapper, params);
            case SELECT:
                return this.executeQuery(mapper, params);
            case UNKNOWN:
            case FLUSH:
        }
        throw new FlackException("unknown command: " + mapper);
    }

    /**
     * 根据配置，处理参数
     *
     * @param mapper 映射配置
     * @param params 参数类型
     * @return 最终使用的参数
     */
    @SuppressWarnings(Warning.UNCHECKED)
    public Map<String, Object> processParameterMap(Mapper mapper, Object params) {
        String parameterType = mapper.getParameterType();
        if (parameterType != null) {
            if (params instanceof Map) {
                return (Map<String, Object>) params;
            } else {
                //do others parameterType
                throw new FlackException("Parameter must be instance of Map!");
            }
        } else {
            ParameterMap parameterMap = mapper.getParameterMap();
            return flake.processParameter(params, parameterMap);
        }
    }

    /**
     * 执行一个更新
     *
     * @param mapper    配置
     * @param parameter 参数
     * @return 查询结果
     */
    @Override
    public Integer executeUpdate(Mapper mapper, Object parameter) {
        //处理参数
        Map<String, Object> params = this.processParameterMap(mapper, parameter);
        //解析 SQL
        PreparedSql preparedSql = flake.processSql(mapper.getTemplate(), params);
        //执行更新
        return flake.update(preparedSql);
    }

    /**
     * 执行一个更新
     *
     * @param mapperGroup 表名
     * @param id          配置ID
     * @param params      参数
     * @return 查询结果
     */
    public Integer executeUpdate(String mapperGroup, String id, Object params) {
        Mapper mapper = this.queryMapperById(mapperGroup, id);
        if (SqlCommandType.UPDATE == mapper.getCommandType()) {
            return this.executeUpdate(mapper, params);
        } else {
            throw new BizException("Command is not a update: " + mapperGroup + "." + id);
        }
    }

    /**
     * 执行一个查询
     *
     * @param mapper    配置
     * @param parameter 参数
     * @return 查询结果
     */
    @Override
    public Object executeQuery(Mapper mapper, Object parameter) {
        String resultType = mapper.getResultType();
        if (resultType != null) {
            return this.queryForMap(mapper, parameter);
        } else {
            return this.queryForObject(mapper, parameter);
        }
    }

    /**
     * 执行一个查询
     *
     * @param mapperGroup 表名
     * @param id          配置ID
     * @param params      参数
     * @return 查询结果
     */
    public Object executeQuery(String mapperGroup, String id, Object params) {
        Mapper mapper = this.queryMapperById(mapperGroup, id);
        if (SqlCommandType.SELECT == mapper.getCommandType()) {
            return this.executeQuery(mapper, params);
        } else {
            throw new BizException("Command is not a select: " + mapperGroup + "." + id);
        }
    }

    /**
     * 执行一个查询
     *
     * @param mapper    配置
     * @param parameter 参数
     * @return 查询结果
     */
    @Override
    public List<Map<String, Object>> queryForMap(Mapper mapper, Object parameter) {
        //处理参数
        Map<String, Object> params = this.processParameterMap(mapper, parameter);
        //执行resultType
        PreparedSql preparedSql = flake.processSql(mapper.getTemplate(), params);
        //执行查询 -- Map
        return flake.query(preparedSql);
    }

    /**
     * 执行一个查询
     *
     * @param mapperGroup 表名
     * @param id          配置ID
     * @param params      参数
     * @return 查询结果
     */
    public List<Map<String, Object>> queryForMap(String mapperGroup, String id, Object params) {
        Mapper mapper = this.queryMapperById(mapperGroup, id);
        if (SqlCommandType.SELECT == mapper.getCommandType()) {
            return this.queryForMap(mapper, params);
        } else {
            throw new BizException("Command is not a select: " + mapperGroup + "." + id);
        }
    }

    /**
     * 执行一个查询
     *
     * @param mapper    配置
     * @param parameter 参数
     * @return 查询结果
     */
    @Override
    public List<Object> queryForObject(Mapper mapper, Object parameter) {
        //处理参数
        Map<String, Object> params = this.processParameterMap(mapper, parameter);
        //解析SQL
        PreparedSql preparedSql = flake.processSql(mapper.getTemplate(), params);
        //执行查询 -- Object
        ResultMap resultMap = mapper.getResultMap();
        return flake.query(preparedSql, resultMap);
    }

    /**
     * 执行一个查询
     *
     * @param mapperGroup 表名
     * @param id          配置ID
     * @param params      参数
     * @return 查询结果
     */
    public List<Object> queryForObject(String mapperGroup, String id, Object params) {
        Mapper mapper = this.queryMapperById(mapperGroup, id);
        if (SqlCommandType.SELECT == mapper.getCommandType()) {
            return this.queryForObject(mapper, params);
        } else {
            throw new BizException("Command is not a select: " + mapperGroup + "." + id);
        }
    }

    /**
     * 执行一个存储过程
     *
     * @param mapper    配置
     * @param parameter 参数，OUT参数返回值会被填充到这个对象
     * @return 查询结果
     */
    @Override
    public Integer processCall(Mapper mapper, Object parameter) {
        ParameterMap parameterMap = mapper.getParameterMap();
        //处理参数
        Map<String, Object> params = flake.processParameter(parameter, parameterMap);
        //解析SQL
        PreparedSql preparedSql = flake.processSql(mapper.getTemplate(), params);
        //执行存储过程
        return flake.call(preparedSql, parameterMap, params, parameter);
    }

    /**
     * 执行一个存储过程
     *
     * @param mapperGroup 表名
     * @param id          配置ID
     * @param params      参数
     * @return 查询结果
     */
    public Integer processCall(String mapperGroup, String id, Object params) {
        Mapper mapper = this.queryMapperById(mapperGroup, id);
        if (StatementType.CALLABLE == mapper.getStatementType()) {
            return this.processCall(mapper, params);
        } else {
            throw new BizException("Command is not a callable: " + mapperGroup + "." + id);
        }
    }

    //setter------------------------------------------

    /**
     * 设置 flake 对象实体
     *
     * @param flake 工具类
     */
    public void setFlake(Flake flake) {
        this.flake = flake;
    }

    /**
     * 映射提供器，做好映射信息扫描即可
     *
     * @param mappingProvider 映射信息扫描
     */
    public void setMappingProvider(MappingProvider mappingProvider) {
        this.mappingProvider = mappingProvider;
    }

    /**
     * 设置分词器
     *
     * @param tokenizer 分词器
     */
    public void setTokenizer(FlakeTokenizer tokenizer) {
        this.flake.setTokenizer(tokenizer);
    }


    /**
     * JdbcTemplate
     *
     * @param jdbcTemplate JdbcTemplate
     */
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.flake.setJdbcTemplate(jdbcTemplate);
    }

    /**
     * 代码模版引擎
     *
     * @param flakeTemplate 模版引擎
     */
    public void setFlakeTemplate(FlakeTemplate flakeTemplate) {
        this.flake.setFlakeTemplate(flakeTemplate);
    }

    /**
     * 设置数据源
     *
     * @param dataSource 数据源
     */
    public void setDataSource(DataSource dataSource) {
        this.flake.getJdbcTemplate().setDataSource(dataSource);
    }


    //getter------------------------------------------

    public Flake getFlake() {
        return flake;
    }

    public MappingProvider getMappingProvider() {
        return mappingProvider;
    }
}
